package com.tre.jdevtemplateboot.aop;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tre.jdevtemplateboot.annotatiaon.RequestLimiterWithRedisValidate;
import com.tre.jdevtemplateboot.common.authority.TokenTakeApart;
import com.tre.jdevtemplateboot.common.dto.PropertiesConfig;
import com.tre.jdevtemplateboot.domain.po.RequestLimiter;
import com.tre.jdevtemplateboot.exception.business.SysFrequentRequestsException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.concurrent.TimeUnit;

/**
 * @author JDev
 */
@Aspect
@Component
@Order(5)
public class RequestLimiterValidationWithRedisAspect {

    private static final String LIMITER_KEY = "limiter:%s:%s";

    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private TokenTakeApart tokenTakeApart;
    @Autowired
    private PropertiesConfig propertiesConfig;


    @Pointcut("@annotation(requestLimiterWithRedisValidate)")
    public void pointcut(RequestLimiterWithRedisValidate requestLimiterWithRedisValidate) {
        throw new UnsupportedOperationException();
    }

    @Around("pointcut(requestLimiterWithRedisValidate)")
    public Object around(ProceedingJoinPoint pjp, RequestLimiterWithRedisValidate requestLimiterWithRedisValidate) throws Throwable {
        String methodName = pjp.getSignature().toLongString();
        int limiterType = requestLimiterWithRedisValidate.limiterType();
        int cycle = requestLimiterWithRedisValidate.cycle();
        int frequency = requestLimiterWithRedisValidate.frequency();
        long currentTime = System.currentTimeMillis();
        String reqHeaderUserId = tokenTakeApart.takeEncryptedUserId();
        String limiterKey = String.format(LIMITER_KEY, reqHeaderUserId, methodName);
        String preFixUserId = getAuthorizationPreFixUserId(limiterKey);
        RequestLimiter requestLimiter = getRequestLimiterWithRedis(preFixUserId);

        long beginTime = 0;
        Integer exFrequency = 0;
        if (requestLimiter != null) {
            beginTime = requestLimiter.getLimiterBeginTime();
            exFrequency = requestLimiter.getLimiterFrequency();
        }

        if (currentTime - beginTime > cycle) {
            updateRequestLimiter(limiterType, limiterKey, currentTime, 1, preFixUserId);
        } else {
            if (exFrequency < frequency) {
                updateRequestLimiter(limiterType, limiterKey, currentTime, exFrequency + 1, preFixUserId);
            } else {
                throw new SysFrequentRequestsException(requestLimiterWithRedisValidate.message());
            }
        }
        return pjp.proceed();
    }

    private void updateRequestLimiter(int limiterType, String limiterKey, long currentTime, int exFrequency, String preFixUserId) throws JsonProcessingException {
        RequestLimiter saveLimiter = new RequestLimiter();
        saveLimiter.setLimiterType(limiterType);
        saveLimiter.setLimiterKey(limiterKey);
        saveLimiter.setLimiterBeginTime(currentTime);
        saveLimiter.setLimiterFrequency(exFrequency);
        ObjectMapper objectMapper = new ObjectMapper();
        String setRequestLimiterWithRedis = objectMapper.writeValueAsString(saveLimiter);
        redisTemplate.opsForValue().set(preFixUserId, setRequestLimiterWithRedis, Long.parseLong(propertiesConfig.getTokenExpire()), TimeUnit.MILLISECONDS);
    }

    private String getAuthorizationPreFixUserId(String userId) {
        return propertiesConfig.getPreUserSubject() + "_request_limit_" + userId;
    }

    private RequestLimiter getRequestLimiterWithRedis(String userId) throws JsonProcessingException {
        RequestLimiter requestLimiter = null;
        String requestLimiterWithRedisValue = redisTemplate.opsForValue().get(userId);
        if (StringUtils.hasLength(requestLimiterWithRedisValue)) {
            ObjectMapper objectMapper = new ObjectMapper();
            requestLimiter = objectMapper.readValue(requestLimiterWithRedisValue, RequestLimiter.class);
        }
        return requestLimiter;
    }


}
